﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace TfsUtils.Ui.Navigation
{
	/// <summary>
	/// questo è un primitivo brocker che serve a smistare i messaggi tra le
	/// varie finestre.
	/// </summary>
	public class Broker : IBroker
	{
        private class Registration
        {
            private Object token;
            private Object action;
            private Boolean _executeInUiThread;

            public Registration(object token, object action, Boolean executeInUiThread)
            {
                this.token = token;
                this.action = action;
                _executeInUiThread = executeInUiThread;
            }

            public object Action
            {
                get { return action; }
            }

            public object Token
            {
                get { return token; }
            }

            public Boolean ExecuteInUiThread
            {
                get { return _executeInUiThread; }
            }
        }


		/// <summary>
		/// 
		/// </summary>
        private readonly Dictionary<Type, List<Registration>> _registeredActions = new Dictionary<Type, List<Registration>>();

		private readonly Dictionary<String, Object> _registeredOperations = new Dictionary<String, Object>();

		/// <summary>
		/// registra per un certo tipo di messaggio una azione da intraprendere.
		/// Praticamente un chiamante può dire al brocker, ogni volta che ti arriva
		/// un messaggio di tipo T io voglio che mi passi il suo contenuto
		/// </summary>
		/// <typeparam name="T">Il tipo di dato che serve anche a discriminare il messaggio</typeparam>
		/// <param name="action">L'azione da chiamare.</param>
		public void RegisterForMessage<T>(Action<Message<T>> action)
		{
			RegisterForMessage(action, null, false);
		}

         public void RegisterForMessage<T>(Action<Message<T>> action, Object token)
         {
             RegisterForMessage(action, token, false);
         }

	    /// <summary>
	    /// Si registra per un messaggio ma passa un token che può essere utilizzato per
	    /// deregistrare tutti gli handler associati.
	    /// </summary>
	    /// <typeparam name="T"></typeparam>
	    /// <param name="action">The action.</param>
	    /// <param name="token">The token.</param>
	    /// <param name="executeInUiThread"></param>
	    public void RegisterForMessage<T>(Action<Message<T>> action, Object token, Boolean executeInUiThread)
        {
            List<Registration> actions;
            if (!_registeredActions.ContainsKey(typeof(T)))
            {
                actions = new List<Registration>();
                _registeredActions.Add(typeof(T), actions);
            }
            else
            {
                actions = _registeredActions[typeof(T)];
            }

            actions.Add(new Registration(token, action, executeInUiThread));
        }

	    /// <summary>
		/// Dichiaro che sono in grado di gestire un messaggio che contiene un 
		/// dato di tipo T e torno un TRet. Per comodità è possibile registrare solamente
		/// un messaggio con tipo di ritorno.
		/// </summary>
		/// <typeparam name="T">Il tipo di dato del messaggio.</typeparam>
		/// <typeparam name="TRet">Il tipo del valore di ritorno</typeparam>
		/// <param name="commandName">Il nome del messaggio per disambiguare il comando
		/// che viene supportato</param>
		/// <param name="func">La funzione che deve essere chiamata</param>
		public void RegisterForOperation<T, TRet>(String commandName, Func<T, TRet> func)
		{
			if (!_registeredOperations.ContainsKey(commandName))
			{
				_registeredOperations.Add(commandName, func);
			}
			else
			{
				//non posso avere più di una registrazione
				throw new ApplicationException("Brocker does not permit more than one handler for an operation");
			}
		}

		/// <summary>
		/// Questo è il messaggio.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="message"></param>
		public void SendMessage<T>(Message<T> message)
		{
            List<Registration> actions;

			if (_registeredActions.TryGetValue(typeof(T), out actions))
			{
				List<Action<Message<T>>> actionThatRaiseException = null;
				foreach (var registration in actions)
				{
                    Action<Message<T>> toExecute = (Action<Message<T>>)registration.Action;
					try
					{
                        if (registration.ExecuteInUiThread)
                        {
                            UiThread.Run(() => toExecute(message));
                        } else
                        {
                            toExecute(message);
                        }
					    
					}
					catch (Exception)
					{
						if (actionThatRaiseException == null)
						{
							actionThatRaiseException = new List<Action<Message<T>>>();
						}
						actionThatRaiseException.Add(toExecute);
					}

					
				}
				if (actionThatRaiseException != null)
				{
					actionThatRaiseException.ForEach(UnRegisterForMessage);
				}
			}
		}

		public void UnRegisterForMessage<T>(Action<Message<T>> action)
		{
			if (_registeredActions.ContainsKey(typeof(T)))
			{
				_registeredActions[typeof (T)].RemoveAll(r => (Action<Message<T>>) r.Action == action);
			}
		}


		/// <summary>
		/// La differenza con <see cref="SendMessage{T}"/> è che in questo caso io sto 
		/// richiedendo una risposta, per cui mi attendo che se qualcuno si è registrato
		/// per questa tipologia di messaggio io sia in grado di ricevere una risposta
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TU"></typeparam>
		/// <param name="message"></param>
		/// <returns></returns>
		public Response<T> Request<T, TU>(Request<TU> message)
		{
			Object actions;

			if (_registeredOperations.TryGetValue(message.CommandName, out actions))
			{
				Func<TU, T> func = (Func<TU, T>) actions;
				return new Response<T>(func(message.Payload));
			}
			else
			{
				return Response<T>.Null;
			}
		}

	    public void UnRegisterAll(object token)
	    {
	        foreach (var registeredAction in _registeredActions)
	        {
	            registeredAction.Value.RemoveAll(r => r.Token == token);
	        }
	    }
	}
}
